package consumer.proxy;

import common.annotation.BaseService;
import common.util.ClassScanner;
import consumer.handler.Router;
import consumer.strategy.RandomRouterStrategy;
import consumer.util.ClientUtil;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDefinition;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.description.type.TypeList;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.*;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
import static net.bytebuddy.matcher.ElementMatchers.named;

/**
 * @ClassName ServiceLoader
 * @Description
 * @Author Jingxiao.Ye
 * @Date 2022/1/16 下午6:59
 * @Version V1.0
 * @Since JDK 11
 */
@Component
public class ServiceLoader implements InitializingBean {

    @Autowired
    private DefaultListableBeanFactory defaultListableBeanFactory;

    @Autowired
    private ApplicationContext applicationContext;

    public static volatile Router router = null;

    @Override
    public void afterPropertiesSet() throws Exception {
        scanAnnotation();
    }

    private Map<String,Object> scanAnnotation(){
        Map<String,Object> resMap = new ConcurrentHashMap<>();
        Map<String,Object> classMap = new ConcurrentHashMap<>();
        ClassScanner.scan("common.api", true, a -> true, a -> {
            BaseService declaredAnnotation = a.getDeclaredAnnotation(BaseService.class);
            if(declaredAnnotation != null){
                //生产代理类
                Object o = null;
                String className = a.getName() + "Impl";
                Class<?> classBuild = null;
                Method[] declaredMethods = a.getDeclaredMethods();
                try {
                    //名称、返回类型、属性
                    DynamicType.Builder<?> nameBuilder = new ByteBuddy().subclass(a).name(className);
                    for (Method declaredMethod : declaredMethods) {
                        Parameter[] parameters = declaredMethod.getParameters();
                        List<Type> types = new ArrayList<>();
                        for (Parameter parameter : parameters) {
                            Class<?> type = parameter.getType();
                            types.add(type);
                        }
                        nameBuilder = nameBuilder.defineMethod(declaredMethod.getName(), declaredMethod.getReturnType(), Modifier.PUBLIC)
                                .withParameters(types).intercept(InvocationHandlerAdapter.of((proxy, method, args) -> getReturn(method.getName(),declaredAnnotation.name() , args)));
                    }
                    classBuild = nameBuilder.make().load(Thread.currentThread().getContextClassLoader()).getLoaded();
                    o = classBuild.newInstance();
                } catch (InstantiationException | IllegalAccessException e) {
                    e.printStackTrace();
                }

                defaultListableBeanFactory.registerSingleton(className,o);

                return true;
            }
            return false;
        });
        return resMap;
    }


    public Object getReturn(String methodName,String className,Object[] args){
        Class[] classes = null;
        if(args != null && args.length > 0){
            classes = new Class[args.length];
            int cnt = 0;
            for (Object arg : args) {
                classes[cnt] = arg.getClass();
                cnt++;
            }
        }
        return ClientUtil.sendMsg(getLazyRouter().getUrl(),className,methodName,classes,args);
    }

    public Router getLazyRouter(){
        if(router == null){
            synchronized (this.getClass()){
                if(router == null){
                    router = new Router(new RandomRouterStrategy());
                }
            }
        }
        return router;
    }

}
